import {FileLoader, Loader} from '../../three.module.js';
import {opentype} from '../libs/opentype.module.min.js';

/**
 * Requires opentype.js to be included in the project.
 * Loads TTF files and converts them into typeface JSON that can be used directly
 * to create THREE.Font objects.
 */

class TTFLoader extends Loader {

  constructor(manager) {

    super(manager);

    this.reversed = false;

  }

  load(url, onLoad, onProgress, onError) {

    const scope = this;

    const loader = new FileLoader(this.manager);
    loader.setPath(this.path);
    loader.setResponseType('arraybuffer');
    loader.setRequestHeader(this.requestHeader);
    loader.setWithCredentials(this.withCredentials);
    loader.load(url, function (buffer) {

      try {

        onLoad(scope.parse(buffer));

      } catch (e) {

        if (onError) {

          onError(e);

        } else {

          console.error(e);

        }

        scope.manager.itemError(url);

      }

    }, onProgress, onError);

  }

  parse(arraybuffer) {

    function convert(font, reversed) {

      const round = Math.round;

      const glyphs = {};
      const scale = (100000) / ((font.unitsPerEm || 2048) * 72);

      const glyphIndexMap = font.encoding.cmap.glyphIndexMap;
      const unicodes = Object.keys(glyphIndexMap);

      for (let i = 0; i < unicodes.length; i++) {

        const unicode = unicodes[i];
        const glyph = font.glyphs.glyphs[glyphIndexMap[unicode]];

        if (unicode !== undefined) {

          const token = {
            ha: round(glyph.advanceWidth * scale),
            x_min: round(glyph.xMin * scale),
            x_max: round(glyph.xMax * scale),
            o: ''
          };

          if (reversed) {

            glyph.path.commands = reverseCommands(glyph.path.commands);

          }

          glyph.path.commands.forEach(function (command) {

            if (command.type.toLowerCase() === 'c') {

              command.type = 'b';

            }

            token.o += command.type.toLowerCase() + ' ';

            if (command.x !== undefined && command.y !== undefined) {

              token.o += round(command.x * scale) + ' ' + round(command.y * scale) + ' ';

            }

            if (command.x1 !== undefined && command.y1 !== undefined) {

              token.o += round(command.x1 * scale) + ' ' + round(command.y1 * scale) + ' ';

            }

            if (command.x2 !== undefined && command.y2 !== undefined) {

              token.o += round(command.x2 * scale) + ' ' + round(command.y2 * scale) + ' ';

            }

          });

          glyphs[String.fromCodePoint(glyph.unicode)] = token;

        }

      }

      return {
        glyphs: glyphs,
        familyName: font.getEnglishName('fullName'),
        ascender: round(font.ascender * scale),
        descender: round(font.descender * scale),
        underlinePosition: font.tables.post.underlinePosition,
        underlineThickness: font.tables.post.underlineThickness,
        boundingBox: {
          xMin: font.tables.head.xMin,
          xMax: font.tables.head.xMax,
          yMin: font.tables.head.yMin,
          yMax: font.tables.head.yMax
        },
        resolution: 1000,
        original_font_information: font.tables.name
      };

    }

    function reverseCommands(commands) {

      const paths = [];
      let path;

      commands.forEach(function (c) {

        if (c.type.toLowerCase() === 'm') {

          path = [c];
          paths.push(path);

        } else if (c.type.toLowerCase() !== 'z') {

          path.push(c);

        }

      });

      const reversed = [];

      paths.forEach(function (p) {

        const result = {
          type: 'm',
          x: p[p.length - 1].x,
          y: p[p.length - 1].y
        };

        reversed.push(result);

        for (let i = p.length - 1; i > 0; i--) {

          const command = p[i];
          const result = {type: command.type};

          if (command.x2 !== undefined && command.y2 !== undefined) {

            result.x1 = command.x2;
            result.y1 = command.y2;
            result.x2 = command.x1;
            result.y2 = command.y1;

          } else if (command.x1 !== undefined && command.y1 !== undefined) {

            result.x1 = command.x1;
            result.y1 = command.y1;

          }

          result.x = p[i - 1].x;
          result.y = p[i - 1].y;
          reversed.push(result);

        }

      });

      return reversed;

    }

    if (typeof opentype === 'undefined') {

      console.warn('THREE.TTFLoader: The loader requires opentype.js. Make sure it\'s included before using the loader.');
      return null;

    }

    return convert(opentype.parse(arraybuffer), this.reversed); // eslint-disable-line no-undef

  }

}

export {TTFLoader};
